/**
 * webpack 工作流程
 * 1. 初始化参数：从配置文件和 Shell 语句中读取与合并设置的参数，得出最终的参数。
 * 2. 开始编译：用上一步得到的参数初始化 Compiler 对象，
 *  2.1 加载所有配置的插件：执行对象的 run 方法开始执行编译；
 *  2.2 确定入口：根据配置文件中 entry 指定的找出入口文件。
 * 3. 编译模块：从入口文件触发，调用所有的 Loader 对模块进行编译，再找出该模块中所依赖的模块，递归本次步骤直到所有的入口文件中所依赖的文件都经过本步骤处理。
 * 4. 完成模块编译：经过第 3 步骤中 loader 编译完所有模块后，得到每个模块编译后的最终内容以及他们之间的依赖关系输出资源，更具入口和模块之间的依赖关系，组装成一个个宝行多个模块的 Chunk，再把每个 Chunk 转换陈一个单独的文件加入到输出列表，这里是可以修改输出内容的最后操作。
 * 5. 输出完成：确定好输出内容后，根据配置确定输出的路径和文件名，把打包后文件内容写入到文件系统。
 */

const { SyncHook } = require('tapable');
class Compiler {
  constructor(options) {
    this.options = options;

    this.hooks = {
      run: new SyncHook(),
      done: new SyncHook()
    }
  }

  run() {
    const modules = []; // 存放所有的模块
    const chunks = []; // 存放所有的代码快
    const files = [];

    this.hooks.run.call(); // 触发 run 钩子函数执行
    // 2.2 根据配置文件中 entry 指定的找出入口文件
    const entry = path.join(this.options.context, this.options.entry);

    // 3. 编译模块
    // 3.1 递归本次步骤
    // 读取入口文件内容 const name = 'tom';
    const entryContent = fs.readFileSync(entry, 'utf8');

    // 通过 loader 转换文件内容
    // 如：js 通过 babel-loader 转换
    const babelLoadr = (source) => {
      // 对源文件内容的编译转换，
      /*
      源码：
      const { title } = require('./title.js')
      console.log('123456', title)
      */

      // 返回编译后的内容
      return `var name = 'tom';`;
    }

    // 这个部分需要不断地处理文件中的 import require 的依赖。不断提取
    const entrySource = babelLoadr(entryContent);
    const entryModule = {
      id: entry,
      source: entrySource
    }
    modules.push(entryModule);

    // 4. 完成模块编译
    // 4.1 根据入口和模块之间的依赖关系，组装成一个个包含多个模块的 Chunk
    const chunk = {
      name: 'main', // 默认 main 名称
      modules
    };
    chunks.push(chunk);

    // 5. 输出完成
    // 5.1 根据配置确定输出的路径和文件名
    const file = {
      file: this.options.output,
      // 打包后的源码
      source: `(() => {
        var __webpack_modules__ = ({
          "./src/title.js":
            ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
              "use strict";
              __webpack_require__.r(__webpack_exports__);
              __webpack_require__.d(__webpack_exports__, {
                "title": () => (title)
              });
              var title = 'title';
            })
        });
        var __webpack_module_cache__ = {};
        function __webpack_require__(moduleId) {
          var cachedModule = __webpack_module_cache__[moduleId];
          if (cachedModule !== undefined) {
            return cachedModule.exports;
          }
          var module = __webpack_module_cache__[moduleId] = {
            exports: {}
          };
          __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
          return module.exports;
        }
        (() => {
          // ./src/main.js
          var _require = __webpack_require__("./src/title.js"),
            title = _require.title;
      
          console.log('123456', title);
        })();
      })();`
    }
    files.push(file);
    // 5.2 把打包后文件内容写入到文件系统
    const outputPath = path.join(this.options.output.path, this.options.output.filename);
    fs.writeFileSync(outputPath, file.source);

    // 完成
    this.hooks.done.call();
  }
}
// 1. 初始化参数
const options = require('./webpack.config.js');
// 2. 开始编译
const compiler = new Compiler(options);
// 2.1 加载所有的配置插件
if (options.plugins && Array.isArray(options.plugins)) {
  for (const plugin of options.plugins) {
    plugin.apply(); //执行插件中的 apply 方法
  }
}
// 2.2 确定入口
compiler.run();
